#include "ArduinoNBIoT.h"
#include <SoftwareSerial.h>
#include <TimeLib.h>

#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

SoftwareSerial mySerial(2, 3); // RX, TX => 2、3 as software serial

U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

char * const extra[] PROGMEM = {
  "-- Designed and implemented by eggfly. ^.^",
  "-- Email: eggfly@qq.com",
};

char serial_buffer[64];
int counter = 0;

void setup() {
  u8g2.begin();
  Serial.begin(9600);
  Serial.setTimeout(5000);
  mySerial.begin(9600);
  mySerial.setTimeout(5000);
  delay(1000);
  Serial.println(F("NB-IoT Arduino Console"));
  print_commands();
  run_cmd("AT");
  delay(100);
  run_cmd("AT+NNMI=1");
  delay(100);
  test();
}

void loop() {
  oled_loop();
  serial_loop();
  check_cron();
  counter++;
}

void test() {
}

void print_commands() {
  Serial.println();
  Serial.println("----------------------------------------------");
  int length = sizeof(commands) / 2 / sizeof(char *);
  char buf[64] = "";
  for (int i = 0; i < length; i++) {
    Serial.print("#"); Serial.print(i + 1); Serial.print(" ");
    strcpy_progmem(buf, &(commands[i][0]));
    Serial.print(buf);
    strcpy_progmem(buf, &(commands[i][1]));
    Serial.print(" ("); Serial.print(buf); Serial.println(")");
  }
  Serial.println();
  Serial.println("Info:");
  int extra_length = sizeof(extra) / sizeof(char *);
  for (int i = 0; i < extra_length; i++) {
    Serial.println(extra[i]);
  }
  Serial.println("----------------------------------------------");
}

inline char * strcpy_progmem(char *buf, char** progmem_ptr) {
  return strcpy_P(buf, (char*)pgm_read_ptr(progmem_ptr));
}

void run_cmd(char * str) {
  Serial.print("[RunCommand] ");
  Serial.print(str);
  Serial.print("\r\n");
  mySerial.print(str);
  mySerial.print("\r");
}

void oled_loop() {
  u8g2.setFont(u8g2_font_ncenB14_tr);
  u8g2.firstPage();
  do {
    u8g2.setCursor(0, 20);
    u8g2.print(F("Hello! "));
    u8g2.print(counter);
  } while (u8g2.nextPage());
}

void serial_loop() {
  int hardwareAvailableBytes = hardwareSerialReadLine();
  if (hardwareAvailableBytes) {
    int parsed = atoi(serial_buffer);
    if (parsed > 0) {
      handle_menu_command(parsed);
    } else {
      mySerial.print(serial_buffer);
      mySerial.print('\r');
    }
  }
  int softwareAvailableBytes = softwareSerialReadLine();
  if (softwareAvailableBytes) {
    Serial.print("[SWSerialOutput] ");
    Serial.print(serial_buffer);
    Serial.println();
    check_new_message();
  }
}

bool running_cron = false;

void check_cron() {
  if (timeStatus() != timeNotSet) {
    if (second() % 20 == 0) {
      if (!running_cron) {
        running_cron = true;
        digitalClockDisplay();
      }
    } else {
      running_cron = false;
    }
  }
}

void check_new_message() {
  if (starts_with(serial_buffer, "+NNMI:")) {
    char *hex_str = strchr(serial_buffer, ',') + 1;
    Serial.println("Received a message:");
    char str_buf[128] = "";
    hex2chars(hex_str, str_buf);
    Serial.print("Message hex value: ");
    Serial.println(hex_str);
    Serial.print("Message string: ");
    Serial.println(str_buf);
    check_sync_time(str_buf);
  }
}

const unsigned long GMT_PLUS_8 PROGMEM = 8 * 60 * 60;

void check_sync_time(char * message) {
  if (starts_with(message, "T")) {
    Serial.println("It's a time format, synchronize time now!");
    unsigned long server_time = atol(message + 1) + GMT_PLUS_8;
    const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013
    if (server_time >= DEFAULT_TIME) { // check the integer is a valid time (greater than Jan 1 2013)
      setTime(server_time); // Sync Arduino clock to the server time
      digitalClockDisplay();
    }
  }
}

inline char one_hex2char(char hex) {
  if (hex >= '0' && hex <= '9') {
    return hex - '0';
  } else if (hex >= 'A' && hex <= 'F') {
    return hex - 'A' + 10;
  } else {
    return 0;
  }
}

inline char two_hex2char(char *hex) {
  char value = (one_hex2char(hex[0]) << 4) + one_hex2char(hex[1]);
  return value;
}

void hex2chars(char *hex, char *str) {
  int len = strlen(hex);
  int str_len = len / 2;
  for (int i = 0; i < str_len; i++) {
    str[i] = two_hex2char(hex + i * 2);
  }
  str[str_len] = '\0';
}

bool starts_with(const char *a, const char *b) {
  if (strncmp(a, b, strlen(b)) == 0) return true;
  return false;
}

int hardwareSerialReadLine() {
  if (Serial.available()) {
    int bytes = Serial.readBytesUntil('\n', serial_buffer, sizeof(serial_buffer));
    serial_buffer[bytes] = '\0';
    return bytes;
  } else {
    return 0;
  }
}

int softwareSerialReadLine() {
  if (mySerial.available()) {
    int bytes = mySerial.readBytesUntil('\n', serial_buffer, sizeof(serial_buffer));
    serial_buffer[bytes] = '\0';
    return bytes;
  } else {
    return 0;
  }
}

void handle_menu_command(int index) {
  Serial.print("You selected item: "); Serial.println(index);
  if (index == 1) {
    print_commands();
  } else if (index == 2) {
    digitalClockDisplay();
  } else {
    char buf[256] = "";
    strcpy_progmem(buf, &(commands[index - 1][0]));
    run_cmd(buf);
  }
}

void digitalClockDisplay() {
  Serial.print(year());
  Serial.print("-");
  Serial.print(month());
  Serial.print("-");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.println();
}

void printDigits(int digits) {
  // utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if (digits < 10) {
    Serial.print('0');
  }
  Serial.print(digits);
}

